Dockerのコンテナ間の名前解決方法が気になったので確認してみた
AWS Fargateを利用することが最近多く、コンテナ間の名前解決にはECS Service Discoveryをよく利用しています。ECS Service Discoveryは平たく言えばRoute53を利用してコンテナ間の名前解決できる仕組みです。
ふと手元に見るとローカルでコンテナ起動しているときはコンテナ間の名前解決をどこで行っているのか?を今まで気にしたことがありませんでした。気にしたことがなかったことに気づけたことは幸いです。手を動かして確認してみましょう。
まとめ
- Dockerはコンテナ間名前解決に利用できるService Discovery機能がある
- コンテナが指定するDNSサーバはループバック用のアドレス範囲にある
127.0.0.11
- ユーザ定義のネットワークを使用している場合に限り利用できる機能
- デフォルトのネットワーク(
bridge
)はService Discoveryをサポートしていない
- コンテナ内にDNSサーバがあるわけではない
- 実態はDockerデーモン内にDNSサーバがあり、レコードを管理をしている
コンテナからDockerデーモンにあるDocker DNS Serverへ問い合わせしている様子の図
下調べ
まず公式ドキュメントを確認します。大切なことはすべて書いてありました。
Docker デーモンは内蔵 DNS サーバを動かし、ユーザ定義ネットワーク上でコンテナがサービス・ディスカバリを自動的に行えるようにします。 コンテナから名前解決のリクエストがあれば、内部 DNS サーバを第一に使います。リクエストがあっても内部 DNS サーバが名前解決できなければ、外部の DNS サーバにコンテナからのリクエストを転送します。 割り当てできるのはコンテナの作成時だけです。内部 DNS サーバが到達可能なのは 127.0.0.11 のみであり、コンテナの resolv.conf に書かれます。 ユーザ定義ネットワーク上の内部 DNS サーバに関しては ユーザ定義ネットワーク用の内部 DNS サーバ をご覧ください。
デフォルトのネットワーク・ブリッジ上では、Docker は自動的なサービス・ディスカバリをサポートしていません
引用: Docker コンテナ・ネットワークの理解 — Docker-docs-ja 19.03 ドキュメント
Docker 1.10 では、docker デーモンに内蔵 DNS サーバを実装しました。これはコンテナ作成時の有効な 名前(name) もしくは ネット・エイリアス(net-alias) または リンク(link) の別名を元にしたサービス・ディスカバリを提供します。
ここまでまとめるとコンテナが問い合わせするDNSサーバは127.0.0.11
である。ループバックアドレスの範囲なのでコンテナ自体にDNSサーバを持たせるのかと思いきや、DNSサーバの実態はDockerデーモン上にある。
画像引用: Docker 概要 — Docker-docs-ja 1.12.RC2 ドキュメント
16-20ページのService Discoveryの説明は図で動作を確認できた数少ない資料でした。
確認してみた
項目 | バージョン |
---|---|
Docker | 20.10.8 |
docker-compose | 1.29.2 |
macOS | 10.15.7 |
準備
検証に利用したファイル類は以下に置いてあります。
bigmuramura/study-docker-local-network
AmazonLinux2コンテナにはdigやnslookupなどのツールのインストールを、Nginxコンテナはデフォルトのindex.htmlを差し替えただけです。
$ docker-compose up -d --build
AmazonLinux2コンテナ(inspect)と、Nginxコンテナ(web)が起動しました。
$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------- study-docker-local-network_inspect_1 /bin/bash Up study-docker-local-network_web_1 /docker-entrypoint.sh ngin ... Up 80/tcp
AmazonLinux2コンテナ
名前解決の確認ためAmazonLinux2コンテナへログインします。
$ docker-compose exec inspect bash
curl
でNginxコンテナ(web)にアクセスしてみます。webのホスト名を名前解決してアクセスできています。
bash-4.2# curl http://web <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Welcome to Abashiri</title> </head> <body> <h1>こんにちは、網走</h1> </body> </html>
ping
もwebのホスト名に対して通りました。
bash-4.2# ping -c 4 web PING web (172.18.0.3) 56(84) bytes of data. 64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=1 ttl=64 time=0.180 ms 64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=2 ttl=64 time=0.167 ms 64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=3 ttl=64 time=0.210 ms 64 bytes from study-docker-local-network_web_1.study-docker-local-network_default (172.18.0.3): icmp_seq=4 ttl=64 time=0.134 ms --- web ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3037ms rtt min/avg/max/mdev = 0.134/0.172/0.210/0.031 ms
調査
引き続きAmazonLinux2コンテナ(inspect)上で進めます。
DNSサーバはドキュメント通り127.0.0.11
でした。また、プライベートIPを引けています。
bash-4.2# nslookup web Server: 127.0.0.11 Address: 127.0.0.11#53 Non-authoritative answer: Name: web Address: 172.18.0.3
resolv.conf
の内容を確認すると、たしかに127.0.0.11
が指定されています。
bash-4.2# cat /etc/resolv.conf nameserver 127.0.0.11 options ndots:0
Dockerのネットワークを確認します。Service Discorveryがサポートされていないデフォルトネットワークのbridgeではなく、新規に作成されたstudy-docker-local-network_defaultがあります。
$ docker network ls NETWORK ID NAME DRIVER SCOPE a6edd8127d60 bridge bridge local e0c54af7d81a host host local 5cecf693c5a1 none null local 9e7f00b6fffe study-docker-local-network_default bridge local
Note:
Dockerのネットワークの説明は以下のリンクがわかりやすかったです。
study-docker-local-network_defaultネットワークの詳細確認します。
$ docker network inspect study-docker-local-network_default
起動中のAmazonLinux2コンテナ(inspect)と、Nginxコンテナ(web)コンテナの情報を確認できました。
[ { "Name": "study-docker-local-network_default", "Id": "9e7f00b6fffe23950583b659af213a328738a6b16b6d2cdf3b27026df7ac47a0", "Created": "2021-09-04T01:45:00.780707704Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "1fc682bbcf689ce76feefdbbd033e0a294b0ad73ec409a90cf4621effad2c932": { "Name": "study-docker-local-network_web_1", "EndpointID": "c434855b3ad53be5cd6b57c9e64ffefb6431f2c66dc05001eea9205ca454db13", "MacAddress": "02:42:ac:12:00:03", "IPv4Address": "172.18.0.3/16", "IPv6Address": "" }, "31f665f961bab7e6c4f1561172a0bca11f22e282781c229fc504194b2cfde96b": { "Name": "study-docker-local-network_inspect_1", "EndpointID": "58951e17713c75980251a45b5a8355ec33563ddfbb9aa081d09834f8ad3d5f30", "MacAddress": "02:42:ac:12:00:02", "IPv4Address": "172.18.0.2/16", "IPv6Address": "" } }, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "study-docker-local-network", "com.docker.compose.version": "1.29.2" } } ]
ここまでは下調べの情報どうりです。
- デフォルトではないネットワークの使用している
- よって、名前解決できている
- コンテナのDNSサーバ指定は
127.0.0.11
レコード登録・削除
DockerデーモンにあるDNSサーバへのレコード登録、削除の挙動がわからないのでいろいろ試してみます。
Webコンテナを10台に増やします。Aレコードが追加されるはずです。
$ docker-compose up -d --scale web=10
web_*
シリーズが増えました。名前解決するとどのような回答が返ってくるのでしょうか。
$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------- study-docker-local-network_inspect_1 /bin/bash Up study-docker-local-network_web_1 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_10 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_2 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_3 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_4 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_5 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_6 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_7 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_8 /docker-entrypoint.sh ngin ... Up 80/tcp study-docker-local-network_web_9 /docker-entrypoint.sh ngin ... Up 80/tcp
複数値回答してくれました。10台すべてのプライベートIPが返ってきてます。コンテナ起動時にAレコードが追加されていることがわかりました。
# AmazonLinux2コンテナより bash-4.2# dig web ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44132 ;; flags: qr rd ra; QUERY: 1, ANSWER: 10, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;web. IN A ;; ANSWER SECTION: web. 600 IN A 172.18.0.12 web. 600 IN A 172.18.0.4 web. 600 IN A 172.18.0.9 web. 600 IN A 172.18.0.7 web. 600 IN A 172.18.0.11 web. 600 IN A 172.18.0.5 web. 600 IN A 172.18.0.10 web. 600 IN A 172.18.0.3 web. 600 IN A 172.18.0.6 web. 600 IN A 172.18.0.8 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Sat Sep 04 03:21:52 UTC 2021 ;; MSG SIZE rcvd: 211
Webコンテナにアクセスした時、リクエストは分散されるのか確認してみます。
curl
を連打します。
# AmazonLinux2コンテナより bash-4.2# curl http://web
Nginx(web)コンテナのアクセスログを確認します。
$ docker-compose logs -f
多少偏りは見られるものの1台に偏るということはありませんでした。curl
コマンドからのアクセスだとランダムのように見えます。サンプル数が10件と少ないためあまり適切ではありませんが、web_3, 4, 8, 10は出現しませんでした。
web_7 | 172.18.0.2 - - [04/Sep/2021:03:29:18 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_5 | 172.18.0.2 - - [04/Sep/2021:03:29:19 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_7 | 172.18.0.2 - - [04/Sep/2021:03:29:25 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_1 | 172.18.0.2 - - [04/Sep/2021:03:29:31 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_1 | 172.18.0.2 - - [04/Sep/2021:03:29:31 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_1 | 172.18.0.2 - - [04/Sep/2021:03:29:32 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_9 | 172.18.0.2 - - [04/Sep/2021:03:29:32 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_7 | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_6 | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-" web_2 | 172.18.0.2 - - [04/Sep/2021:03:29:33 +0000] "GET / HTTP/1.1" 200 168 "-" "curl/7.76.1" "-"
次はコンテナ起動に追加されたAレコードはどのタイミング削除されるのか確認します。
Nginx(web)コンテナを次々と止めてみます。
$ docker stop study-docker-local-network_web_1
途中経過、返ってくるプライベートIPが減っています。
# AmazonLinux2(inspect)コンテナより bash-4.2# dig web ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9234 ;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;web. IN A ;; ANSWER SECTION: web. 600 IN A 172.18.0.6 web. 600 IN A 172.18.0.11 web. 600 IN A 172.18.0.10 web. 600 IN A 172.18.0.9 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Sat Sep 04 03:36:51 UTC 2021 ;; MSG SIZE rcvd: 97
Nginx(web)コンテナをすべて落としました。
$ docker-compose ps Name Command State Ports -------------------------------------------------------------------------------------- study-docker-local-network_inspect_1 /bin/bash Up study-docker-local-network_web_1 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_10 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_2 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_3 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_4 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_5 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_6 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_7 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_8 /docker-entrypoint.sh ngin ... Exit 0 study-docker-local-network_web_9 /docker-entrypoint.sh ngin ... Exit 0
プライベートIPは一切引けなくなりました。ANSWER SECTION:
が存在していないですね。コンテナ停止と共に動的にAレコードが削除されていることがわかりました。
# AmazonLinux2(inspect)コンテナより bash-4.2# dig web ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> web ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 21717 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;web. IN A ;; Query time: 49 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Sat Sep 04 03:38:24 UTC 2021 ;; MSG SIZE rcvd: 21
ネットワークの違い
冒頭の以下の引用文が気になったので違いを調べます。
デフォルトのネットワーク・ブリッジ上では、Docker は自動的なサービス・ディスカバリをサポートしていません
まずデフォルトのbridge
ネットワークでAmazonLinux2コンテナ(inspect)を起動します。
$ docker run --rm -it study-docker-local-network_inspect bash
ループバック用のアドレス範囲にあるアドレスのDNSサーバ127.0.0.11
が指定されていません。DNSサーバの指定に違いがあるんですね。
bash-4.2# cat /etc/resolv.conf # DNS requests are forwarded to the host. DHCP DNS options are ignored. nameserver 192.168.65.5
次にユーザ定義のネットワーク(study-docker-local-network_default
)で起動してみます。このネットワークは今までdocker-composeで使っていたものです。
$ docker run --rm -it --net=study-docker-local-network_default study-docker-local-network_inspect bash
127.0.0.11
のDNSサーバが指定されています。
bash-4.2# cat /etc/resolv.conf nameserver 127.0.0.11 options ndots:0
/etc/resove.conf
の内容に違いが見られることを確認できました。コンテナを起動するネットワークがデフォルト(bridge)か、ユーザ定義のネットワークかの違いでDNSサーバの指定が異なる。
おわりに
Dockerのコンテナ間通信のときのホスト名の名前解決はService Discoveryという仕組みを持っていた。ちなみにAWSのECSで使われるコンテナ間の名前解決の仕組みはECS Service Discoveryで名前が似ています。 ECS Service Discoveryはオートスケールやタスク数設定によるコンテナ増減に連動して、Route 53のレコードが自動的に書き換えてくれる。DockerのService Discoveryもコンテナ数の増減に連動して、DockerデーモンにあるDNSサーバのレコードを自動的に書き換えていた。 柔軟にコンテナの名前解決できる仕組みという意味では同じ印象です。
ECS Service Discoveryは今後も検証する機会がありそうだけど、ローカルネットワークをまた検証する機会はあまりなさそうです。気になったときにまた調べたいと思います。
細かい違いとしては複数値の回答数が異なる点には気がついたのですが、ECS Service Discoveryの仕様を把握できていなかったので改めてサービス検出を読み直す必要がありました。巡り巡ってECS Service Discoveryの勉強し直せて良い機会でした。
クライアントが複数値回答ルーティングを使用して DNS リクエストを行うと、Route 53 は DNS クエリに応答して特定のドメイン名の正常なレコードを最大 8 個ランダムに選択して返します。
複数値ルーティングポリシーとシンプルルーティングポリシーを理解する
残った疑問
127.0.0.11
に問い合わせた際になぜ再帰的に名前解決する経路がわからなかったのだろうか。
bash-4.2# dig +trace google.com ; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> +trace google.com ;; global options: +cmd ;; Received 17 bytes from 127.0.0.11#53(127.0.0.11) in 5 ms